﻿using Common.Framework;
using Common.Framework.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace DBUtil.Builders
{
    public class SelectTreeBuilder<T, Dto> : BaseBuilder where T : class, new() where Dto : class, new()
    {
        private readonly EntityInfo entityInfo;
        private readonly EntityInfo dtoEntityInfo;
        private readonly EntityPropertyInfo idEntityPropertyInfo;
        private readonly EntityPropertyInfo parentIdEntityPropertyInfo;
        private readonly EntityPropertyInfo childrenEntityPropertyInfo;

        internal SelectTreeBuilder(DBAccess db, Expression<Func<T, object>> idSelector, Expression<Func<T, object>> parentIdSelector, Expression<Func<Dto, IList<Dto>>> childrenSelector) : base(db)
        {
            Ensure.NotNull(idSelector, nameof(idSelector));
            Ensure.NotNull(parentIdSelector, nameof(parentIdSelector));
            Ensure.NotNull(childrenSelector, nameof(childrenSelector));
            this.entityInfo = db.GetEntityInfoInternal<T>();
            this.dtoEntityInfo = db.GetEntityInfoInternal<Dto>();

            //id
            if (idSelector.Body is UnaryExpression unary && unary.Operand is MemberExpression member)
            {
                this.idEntityPropertyInfo = entityInfo.EntityPropertyInfos.FirstOrDefault(i => i.PropNamePure == member.Member.Name);
            }
            else if (idSelector.Body is MemberExpression member2)
            {
                this.idEntityPropertyInfo = entityInfo.EntityPropertyInfos.FirstOrDefault(i => i.PropNamePure == member2.Member.Name);
            }
            Ensure.NotNull(this.idEntityPropertyInfo, $"无法根据 idSelector 从 {entityInfo.TypeClassFullName} 中提取 Id 属性!");

            //parentId
            if (parentIdSelector.Body is UnaryExpression unary2 && unary2.Operand is MemberExpression member3)
            {
                this.parentIdEntityPropertyInfo = entityInfo.EntityPropertyInfos.FirstOrDefault(i => i.PropNamePure == member3.Member.Name);
            }
            else if (parentIdSelector.Body is MemberExpression member2)
            {
                this.parentIdEntityPropertyInfo = entityInfo.EntityPropertyInfos.FirstOrDefault(i => i.PropNamePure == member2.Member.Name);
            }
            Ensure.NotNull(this.parentIdEntityPropertyInfo, $"无法根据 parentIdSelector 从 {entityInfo.TypeClassFullName} 中提取 ParentId 属性!");

            //children
            if (childrenSelector.Body is UnaryExpression unary3 && unary3.Operand is MemberExpression member4)
            {
                this.childrenEntityPropertyInfo = dtoEntityInfo.EntityPropertyInfos.FirstOrDefault(i => i.PropNamePure == member4.Member.Name);
            }
            else if (childrenSelector.Body is MemberExpression member2)
            {
                this.childrenEntityPropertyInfo = dtoEntityInfo.EntityPropertyInfos.FirstOrDefault(i => i.PropNamePure == member2.Member.Name);
            }
            Ensure.NotNull(this.parentIdEntityPropertyInfo, $"无法根据 childrenSelector 从 {entityInfo.TypeClassFullName} 中提取 Children 属性!");
        }

        internal SelectTreeBuilder(DBAccess db, string idPropName, string parentIdPropName, string childrenPropName) : base(db)
        {
            Ensure.NotNullOrEmptyOrWhiteSpace(idPropName, nameof(idPropName));
            Ensure.NotNullOrEmptyOrWhiteSpace(parentIdPropName, nameof(parentIdPropName));
            Ensure.NotNullOrEmptyOrWhiteSpace(childrenPropName, nameof(childrenPropName));
            this.entityInfo = db.GetEntityInfoInternal<T>();
            this.dtoEntityInfo = db.GetEntityInfoInternal<Dto>();

            //id
            this.idEntityPropertyInfo = entityInfo.EntityPropertyInfos.FirstOrDefault(i => i.PropNamePure == idPropName).IfNullUse(entityInfo.EntityPropertyInfos.FirstOrDefault(i => string.Equals(i.PropNamePure, idPropName, StringComparison.OrdinalIgnoreCase)));
            Ensure.NotNull(this.idEntityPropertyInfo, $"无法根据 idPropName 从 {entityInfo.TypeClassFullName} 中提取 Id 属性!");

            //parentId
            this.parentIdEntityPropertyInfo = entityInfo.EntityPropertyInfos.FirstOrDefault(i => i.PropNamePure == parentIdPropName).IfNullUse(entityInfo.EntityPropertyInfos.FirstOrDefault(i => string.Equals(i.PropNamePure, parentIdPropName, StringComparison.OrdinalIgnoreCase)));
            Ensure.NotNull(this.idEntityPropertyInfo, $"无法根据 parentIdPropName 从 {entityInfo.TypeClassFullName} 中提取 ParentId 属性!");

            //children
            this.childrenEntityPropertyInfo = entityInfo.EntityPropertyInfos.FirstOrDefault(i => i.PropNamePure == childrenPropName).IfNullUse(entityInfo.EntityPropertyInfos.FirstOrDefault(i => string.Equals(i.PropNamePure, childrenPropName, StringComparison.OrdinalIgnoreCase)));
            Ensure.NotNull(this.idEntityPropertyInfo, $"无法根据 childrenPropName 从 {entityInfo.TypeClassFullName} 中提取 Children 属性!");
        }

        protected List<object> Filters { get; set; } = [];

        #region WhereSeg & Where
        public virtual SelectTreeBuilder<T, Dto> WhereSeg<TAny>(Expression<Func<TAny, bool>> filter)
        {
            Ensure.NotNull(filter, nameof(filter));
            Filters.Add(filter);
            return this;
        }
        public virtual SelectTreeBuilder<T, Dto> WhereSegIf<TAny>(bool condition, Expression<Func<TAny, bool>> filter)
        {
            if (!condition) return this;
            return WhereSeg(filter);
        }
        public virtual SelectTreeBuilder<T, Dto> Where(string filter)
        {
            Ensure.NotNullOrEmptyOrWhiteSpace(filter);
            Filters.Add(filter);
            return this;
        }
        public virtual SelectTreeBuilder<T, Dto> WhereIf(bool condition, string filter)
        {
            if (!condition) return this;
            return Where(filter);
        }
        public virtual SelectTreeBuilder<T, Dto> Where(Expression<Func<T, bool>> filter)
        {
            Ensure.NotNull(filter, nameof(filter));
            Filters.Add(filter);
            return this;
        }
        public virtual SelectTreeBuilder<T, Dto> WhereIf(bool condition, Expression<Func<T, bool>> filter)
        {
            if (!condition) return this;
            return Where(filter);
        }
        #endregion

        private const string MAGICSTR = "##^_^";
        private const string MAGICSTR2 = ",##^_^";
        public string ToSql()
        {
            //当没有条件时表示加载全部节点, 此时 向下/上 的扩展都没有意义
            if (Filters.Count == 0) return db.Select<T>().ToSqlList();

            var select = db.Select<T>().Alias("t").SetFilters(Filters) as SelectBuilder<T>;
            if (parentLimit > 0 || subLimit > 0) select.AddExtraSelectSeg(MAGICSTR);
            var simpleSql = select.ToSqlList().TrimEnd(';');
            var simpleSqlClause = db.Select<T>().Alias("t2").ToSqlSelectClause();
            var cols = entityInfo.EntityPropertyInfos.Where(i => i.IsColumn).Select(i => i.PropNameSeg).ToStringSeparated(",");
            var sb = new StringBuilder().Append("with recursive ");

            //parentCte
            var parentSimpleSql = simpleSql;
            if (parentLimit <= 0 && subLimit > 0)
            {
                parentSimpleSql = parentSimpleSql.Replace(MAGICSTR2, "");
            }
            else if (parentLimit > 0)
            {
                parentSimpleSql = parentSimpleSql.Replace(MAGICSTR, "0");
            }
            var parentCte = $"""
                cteParent ({cols}{(parentLimit > 0 ? ",`_parentLimit`" : "")}) AS
                (
                  {parentSimpleSql}
                  union
                  select {simpleSqlClause}{(parentLimit > 0 ? ",`_parentLimit`+1" : "")} from {entityInfo.TableNameSeg} t2,cteParent where cteParent.{parentIdEntityPropertyInfo.PropNameSeg}=t2.{idEntityPropertyInfo.ColumnNameSeg}{(parentLimit > 0 ? $" and `_parentLimit`<{parentLimit}" : "")}
                )
                """;

            //subCte
            var subSimpleSql = simpleSql;
            if (subLimit <= 0 && parentLimit > 0)
            {
                subSimpleSql = subSimpleSql.Replace(MAGICSTR2, "");
            }
            else if (subLimit > 0)
            {
                subSimpleSql = subSimpleSql.Replace(MAGICSTR, "0");
            }
            var childrenCte = $"""
                cteChildren ({cols}{(subLimit > 0 ? ",`_subLimit`" : "")}) AS
                (
                  {subSimpleSql}
                  union
                  select {simpleSqlClause}{(subLimit > 0 ? ",`_subLimit`+1" : "")} from {entityInfo.TableNameSeg} t2,cteChildren where cteChildren.{idEntityPropertyInfo.PropNameSeg}=t2.{parentIdEntityPropertyInfo.ColumnNameSeg}{(subLimit > 0 ? $" and `_subLimit`<{subLimit}" : "")}
                )
                """;

            if (spreedMode == EnumTreeSpreedMode.Both)
            {
                sb.Append(parentCte).AppendLine(",").Append(childrenCte).AppendLine()
                    .AppendLine("select * from cteParent").AppendLine("union").Append("select * from cteChildren;");
            }
            else if (spreedMode == EnumTreeSpreedMode.Parent)
            {
                sb.Append(parentCte).AppendLine().Append("select * from cteParent;");
            }
            else
            {
                sb.Append(childrenCte).AppendLine().Append("select * from cteChildren;");
            }
            var sql = sb.ToString();
            sb.Clear();
            return sql;
        }

        public List<Dto> ToList()
        {
            var sql = ToSql();
            var list = db.SelectModelList<Dto>(sql);
            var compare = parentIdEntityPropertyInfo.Type.GetDefaultEqualityComparer();
            var tree = list.FetchToTree(idEntityPropertyInfo.PropNamePure, parentIdEntityPropertyInfo.PropNamePure, childrenEntityPropertyInfo.PropNamePure, compare);
            if (visitAction != null) tree.VisitTree(childrenEntityPropertyInfo.PropNamePure, visitAction);
            return tree;
        }

        public async Task<List<Dto>> ToListAsync()
        {
            var sql = ToSql();
            var list = await db.SelectModelListAsync<Dto>(sql);
            var compare = parentIdEntityPropertyInfo.Type.GetDefaultEqualityComparer();
            var tree = list.FetchToTree(idEntityPropertyInfo.PropNamePure, parentIdEntityPropertyInfo.PropNamePure, childrenEntityPropertyInfo.PropNamePure, compare);
            if (visitAction != null) tree.VisitTree(childrenEntityPropertyInfo.PropNamePure, visitAction);
            return tree;
        }

        private EnumTreeSpreedMode spreedMode = EnumTreeSpreedMode.Both;
        public SelectTreeBuilder<T, Dto> SetSpreedMode(EnumTreeSpreedMode mode)
        {
            this.spreedMode = mode;
            return this;
        }

        private int subLimit;
        private int parentLimit;
        public SelectTreeBuilder<T, Dto> SetSpreedLimit(int limit, EnumTreeSpreedMode mode)
        {
            if (mode == EnumTreeSpreedMode.Sub)
            {
                this.subLimit = limit;
            }
            else if (mode == EnumTreeSpreedMode.Parent)
            {
                this.parentLimit = limit;
            }
            else
            {
                this.subLimit = this.parentLimit = limit;
            }
            return this;
        }

        private Action<VisitTreeContext<Dto>> visitAction = null;
        public SelectTreeBuilder<T, Dto> VisitTreeInMemory(Action<VisitTreeContext<Dto>> action)
        {
            this.visitAction = action;
            return this;
        }
    }

    public class SelectTreeBuilder<T> : SelectTreeBuilder<T, T> where T : class, new()
    {
        internal SelectTreeBuilder(DBAccess db, Expression<Func<T, object>> idSelector, Expression<Func<T, object>> parentIdSelector, Expression<Func<T, IList<T>>> childrenSelector) : base(db, idSelector, parentIdSelector, childrenSelector)
        {
        }
        internal SelectTreeBuilder(DBAccess db, string idPropName, string parentIdPropName, string childrenPropName) : base(db, idPropName, parentIdPropName, childrenPropName)
        {
        }
    }
}
